// locutor
// (c) 2009, Normen Müller, normen.mueller@gmail.com  
// $Id: D.scala 4 2009-11-06 19:59:06Z normen.mueller $
package locutor.model

import java.io.File
import java.net.URL

import scaup.Views._
import scaup.data._

import scalaxx.diff.Command
import scalaxx._

import locutor.cia.ImpactGraph
import locutor.parsing.model.dg.DependencyGraphParser

/** Companion to <code>DependencyGraph</code> class.
 * 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.1 */
object DependencyGraph {

  def parse(description: File): DependencyGraph = DependencyGraphParser.parse(description) 
  
}

/** A dependency graph.
 * 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.1 */
class DependencyGraph private [locutor] (val name: String, val reference: File, val revision: Int, val model: GraphModel)
  extends MultiGraph
  with MultiGraph#NodeLabels[Map[String, String]]
  with MultiGraph#EdgeLabels[Map[String, String]]
  with MultiGraph#Directions {
    
    self: DependencyGraph =>
    
  // URL/XPath -> V
  private val nodes = collection.mutable.HashMap.empty[String, V]
  
  def V = Set.empty[V] ++ nodes.values
  
  private var edges: List[E] = Nil
  
  def E = Set.empty[E] ++ edges
  
  //@todo check for duplicates
  def + (id: String, url: URL, xpath: path.Expr, attributes: A) = {
    val infom = Infom(id, url, xpath, attributes)
    nodes += {( url.toString + xpath, infom )}
    ids   += {( id, infom )}
    this
  }
  
  // Ident -> V
  private val ids = collection.mutable.HashMap.empty[String, V] 
  
  def \ (id: String) = ids get id //V \? { case Infom(_, _, as) if as.get("$") == Some(id) => true }
  
  //@todo partial ordering of XPath1.0 expressions for identification of surrounding INFOM
  def \ (xpath: path.Expr) = nodes get (reference.toURL.toString + xpath)
  
  import cia.Rippler.{ripple}
  
  def ↩ (δ: List[Command]) = new {
    def having(rules: RuleSystem) = δ match {
      case Nil => Map.empty[Command, ImpactGraph]
      case _   => ripple(δ) withKnowledgeBase (self, rules) 
    }      
  } 
  
  type A = Map[String, String]
    
  type V = Infom
  
  private [DependencyGraph] case class Infom(val id: String, val url: URL, val xpath: path.Expr, val label: A) extends Node { self: V =>
    def ->(v: V): E = (this connectTo v)(Map())
    def connectTo(v: V)(attributes: A): E = {
      val d = Dependency(this, v, attributes)
      edges ::= d
      d
    } 
    
    def inDegree = incoming.size
    def incoming = E filter {case Dependency(_, target, _) if target == this => true case _ => false}
      
    def outDegree = outgoing.size
    def outgoing  = E filter {case Dependency(source, _, _) if source == this => true case _ => false}
        
    def degree = inDegree + outDegree
    
    override def toString = {
      val buf = new StringBuilder
      buf append id append ":" append label("type") append "("
      
      buf append "url=\"" + url + "\"," append "xpath=\"" + xpath + "\""
      
      val elems = (label - "type").keys
      if (elems.hasNext) {
        val elem = elems.next
        buf append "," append elem append "=" append label(elem)
      }
      while (elems.hasNext) {
        val elem = elems.next
        buf append ","
        buf append elem append "=" append label(elem)
      }      

      buf append ")"
      buf.toString
    }
  }
  
  type E = Dependency
  
  private [DependencyGraph] case class Dependency(val source: V, val target: V, label: A) extends Edge {
    override def toString = {
      val buf = new StringBuilder
      buf append source.id append " -:" append label("type") append "("
      
      val elems = (label - "type").keys
      if (elems.hasNext) {
        val elem = elems.next
        buf append elem append "=" append label(elem)
      }
      while (elems.hasNext) {
        val elem = elems.next
        buf append ","
        buf append elem append "=" append label(elem)
      }      

      buf append ")" append "-> " append target.id
      buf.toString
    }
  }
  
}
